package solver;

import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;

import model.Problem;
import model.Service;
import model.Solution;
import model.SolverEnvironment;
import util.Chronograph;
import views.VisualizeJFrame;
import etm.core.configuration.EtmManager;
import etm.core.monitor.EtmMonitor;

public class ProblemDecompositionMathSolver extends BaseSolver {
//	private static final EtmMonitor etmMonitor = EtmManager.getEtmMonitor();

	static {
		String os_name = System.getProperty("os.name").toLowerCase();
		String os_arch = System.getProperty("os.arch").toLowerCase();
		if ((os_name.indexOf("win") >= 0) && (os_arch.indexOf("64") > 0))
			System.loadLibrary("libwin64//jnilinearsolver");
		else
			System.loadLibrary("jnilinearsolver");
	}

	private Solution best_recorded_solution;
	private boolean useGRB;
	private boolean visualize = true;
	private int round = 1;
	private String solverName = "solver unknown";
	private int nop = 40;
	private int noem = 15;
	private int non = 2;
	private int time_limit_single_problem = 5;

	public void setTime_limit_single_problem(int time_limit_single_problem) {
		this.time_limit_single_problem = time_limit_single_problem;
	}

	private int delta_m = 0;
	private int delta_p = 0;

	SelectionAgent4 sa4;
	VisualizeJFrame app;
	boolean includeDependencyConstraints = true;
	boolean saveLP = false;

	public void setLimitXVars(int limitXVars) {
		if (limitXVars != -1)
			sa4.setLimitXvars(limitXVars);
	}

	public void setSaveLP(boolean saveLP) {
		this.saveLP = saveLP;
	}

	public ProblemDecompositionMathSolver(SolverEnvironment env,
			boolean useGRB, boolean visualize) {
		super(env);
		this.visualize = visualize;
		sa4 = new SelectionAgent4(env);
		this.useGRB = useGRB;
		if (useGRB)
			solverName = "Gurobi";
		if (visualize)
			env.getProblem()
					.createMachinesOrderedListsByLocationAndNeighborhood();
	}

	public void setIncludeDependencyConstraints(
			boolean includeDependencyConstraints) {
		this.includeDependencyConstraints = includeDependencyConstraints;
		sa4.setDependencyConstraintsNeeded(includeDependencyConstraints);
	}

	public void set_nop_noem_non(int nop, int noem, int non) {
		this.nop = nop;
		this.noem = noem;
		this.non = non;
		if (this.non == 0)
			this.non = 1;
		else if (this.non > noem)
			this.non = noem;
		log.debug(String.format("NOP=%d, NOEM=%d, NON=%d", this.nop, this.noem,
				this.non));
	}

	public void revertToBestSolution() {
		env.setCurrentSolution(best_recorded_solution);
		env.getFullValidator().computeCost();
	}

	public int solveWithTimeLimit(int seconds) {
		if (!visualize)
			log.debug("No visualization of the solution process");
		int consecutive_time_limit_fails = 0;
		int consecutive_time_limit_successes = 0;
		int improvement_counter = 0;
		Chronograph chronos = new Chronograph();
		chronos.reset();
		env.setCurrentSolution(getCurrentSolution());
		getFullValidator().computeCost();
		long global_best_cost = getFullValidator().getInitialSolutionCost();

		if (visualize) {
			app = new VisualizeJFrame(getProblem(), getInitialSolution(),
					getCurrentSolution(), global_best_cost);
		}
		chronos.lap();
		while (chronos.getElapsedTimeInSeconds() < seconds) {
			log.info(String
					.format("########################### ROUND %d ###########################",
							round));
			log.debug(String.format("Current solution identity=%d",
					getCurrentSolution().identity));
			long time_left_seconds = seconds
					- chronos.getElapsedTimeInSeconds();
			log.info(String.format("Time left = %d", time_left_seconds));
			List<Integer> selectedProcesses;
			int number_of_selected_processes = nop + delta_p;
			int number_of_selected_machines = noem + delta_m;
			selectedProcesses = sa4.selectFullRandomProcessesAndMachinesFast(
					number_of_selected_processes, number_of_selected_machines,
					non);
			log.debug("\n"
					+ getProblem().getSelectedProcessesAndEligibleMachines(
							selectedProcesses, false, env));

			// ############## START TEST ########
			int as = 0;
			for (int s = 0; s < env.getProblem().S; s++) {
				boolean exists = false;
				Service service = env.getProblem().getServices().get(s);
				for (Integer p : service.getProcesses()) {
					if (selectedProcesses.contains(p)) {
						exists = true;
					}
				}
				if (exists) {
					as++;
				}
			}
			if (as <= 1)
				continue;
			// ############## END TEST ########

			if (visualize) {
				app.setSelectedProcesses(selectedProcesses);
			}
			long cutoff = getFullValidator().getNewSolutionCost();
			BaseExtraLeanIPModelBuilder modelBuilder;
			if (useGRB) {
				modelBuilder = new GrbExtraLeanIPModelBuilder(getProblem(),
						selectedProcesses, true, time_limit_single_problem,
						cutoff);
			} else {
				modelBuilder = new OrtExtraLeanIPModelBuilder(getProblem(),
						selectedProcesses, false, time_limit_single_problem,
						cutoff);
				solverName = ((OrtExtraLeanIPModelBuilder) modelBuilder)
						.getSolverVersion();
			}
			modelBuilder.setInitialSolution(getInitialSolution());
			modelBuilder.setCurrentSolution(getCurrentSolution());
			modelBuilder
					.includeDependencyHardConstraints(includeDependencyConstraints);

			// modelBuilder.includeConflictHardConstraints(true);
			// modelBuilder.includeAssignmentHardConstraints(true);
			// modelBuilder.includeCapacityHardConstraints(true);
			// modelBuilder.includeSpreadHardConstraints(true);
			// modelBuilder.includeDependencyHardConstraints(true);
			// modelBuilder.includeTransientHardConstraints(true);
			// modelBuilder.includeLoadCostSoftConstraints(true);
			// modelBuilder.includeBalanceCostSoftConstraints(true);
			// modelBuilder.includeProcessMoveCostSoftConstraints(true);
			// modelBuilder.includeMachineMovedCostSoftConstraints(true);
			// modelBuilder.includeServiceMoveCostSoftConstraints(true);

			boolean solved = modelBuilder.solve(saveLP);
			int solver_status = modelBuilder.getSolverStatus();
			if (solver_status == 0 || solver_status == 1) {
				// modelBuilder.updateSolutionDirect(env, selectedProcesses);
				modelBuilder.updateSolution(env, selectedProcesses);
				if (isIncomplete(getPotentialSolution())) {
					log.info("" + this.getClass().getSimpleName());
				} else {
					getFullValidator().computeCost();
					long potential_solution_cost = getFullValidator()
							.getNewSolutionCost();
					log.debug(String
							.format("***GLOBAL BEST COST=%,d POTENTIAL SOLUTION COST=%,d",
									global_best_cost, potential_solution_cost));
					if (potential_solution_cost > cutoff) {
						Toolkit.getDefaultToolkit().beep();
						log.info(String
								.format("Potential solution cost=%,d should never be greater than cutoff=%,d",
										potential_solution_cost, cutoff));
//						System.exit(-1);
					}

					if (visualize) {
						app.setMessage(String.format("R%d COST=%,d GBC=%,d %s",
								round, potential_solution_cost,
								global_best_cost, solverName));
					}
					if ((potential_solution_cost < global_best_cost)
							|| ((potential_solution_cost == global_best_cost) && (best_recorded_solution != null))) {
						if (getFullValidator().isFeasible()) {
							if (global_best_cost == potential_solution_cost) {
								if (best_recorded_solution
										.isIdentical(getPotentialSolution())) {
									log.info(String
											.format(":| SAME solution (round=%d, improvement=%d, number of moved processes=%d) %,d",
													round,
													improvement_counter,
													getNumberOfMovedProcesses(getPotentialSolution()),
													potential_solution_cost));
									getFullValidator()
											.printNewSolutionCostAnalysis("NEW");
								} else {
									log.info(String
											.format(":| EQUIVALENT solution found (round=%d, improvement=%d, number of moved processes=%d) %,d",
													round,
													improvement_counter,
													getNumberOfMovedProcesses(getPotentialSolution()),
													potential_solution_cost));
									getFullValidator()
											.printNewSolutionCostAnalysis("NEW");
								}
							} else {
								improvement_counter++;
								log.info(String
										.format(":) BETTER solution found (round=%d, improvement=%d, number of moved processes=%d) %,d (reduced by %,d)",
												round,
												improvement_counter,
												getNumberOfMovedProcesses(getPotentialSolution()),
												potential_solution_cost,
												global_best_cost
														- potential_solution_cost));
								getFullValidator()
										.printNewSolutionCostAnalysis("NEW");
							}
							long solver_objective_value = (long) Math
									.round(modelBuilder
											.getSolverObjectiveValue());
							if (solver_objective_value != potential_solution_cost) {
								log.info(String
										.format("@@@@@@==> solver objective value = %,d potential solution cost = %,d",
												solver_objective_value,
												potential_solution_cost));
								// System.exit(-1);
							}

							global_best_cost = potential_solution_cost;
							setCurrentSolution(getPotentialSolution().copy());
							chronos.lap();
							// saveSolution(global_best_cost,
							// chronos.getElapsedTimeInSeconds(), false);
							best_recorded_solution = getCurrentSolution()
									.copy();
							if (visualize) {
								app.setCurrentSolution(getCurrentSolution());
								app.setMessage(String.format(
										"R%d COST=%,d GBC=%,d %s *", round,
										potential_solution_cost,
										global_best_cost, solverName));
								app.setCostValue(global_best_cost);
							}
						} else {
							Problem.VERBOSE = true;
							getFullValidator().isFeasible();
							log.info("NOT FEASIBLE SOLUTION :(");
							// System.exit(-1);
						}
					} else {
						if (visualize) {
							app.setCurrentSolution(getCurrentSolution());
							app.setMessage(String.format(
									"R%d COST=%,d GBC=%,d %s", round,
									potential_solution_cost, global_best_cost,
									solverName));
							app.setCostValue(potential_solution_cost);
						}
					}
				}
				consecutive_time_limit_fails = 0;
				consecutive_time_limit_successes++;
				if (consecutive_time_limit_successes == 50) {
					increaseSubproblemSize();
					consecutive_time_limit_successes = 0;
				}
			} else {
				log.info("IP solver failed to find an integer solution.");
				Problem.VERBOSE = true;
				getFullValidator().isFeasible();
				Problem.VERBOSE = false;
				consecutive_time_limit_fails++;
				consecutive_time_limit_successes = 0;
				if (consecutive_time_limit_fails == 3) {
					decreaseSubproblemSize();
					consecutive_time_limit_fails = 0;
				}
				// System.exit(-1);
			}
			round++;
			chronos.lap();
			Problem.RANDOM_SEED = getRandom().nextLong();
		}
		if (visualize) {
			app.setMessage(String.format("R%d GBC=%,d END", round,
					global_best_cost));
		}
		return chronos.getElapsedTimeInSeconds();
	}

	private void increaseSubproblemSize() {
		sa4.changeLimitXVarsByPercent(0.05);
		delta_p += 5;
		delta_m++;
		log.debug(String.format(
				"INCREASE SUBPROBLEM SIZE CALLED: XVARS=%d NOP=%d NOM=%d",
				sa4.getLimitXvars(), nop + delta_p, noem + delta_m));
	}

	static final int MIN_XVARS = 300;
	static final int MIN_NOP = 40;
	static final int MIN_NOEM = 20;

	private void decreaseSubproblemSize() {
		if (sa4.getLimitXvars() > MIN_XVARS)
			sa4.changeLimitXVarsByPercent(-0.05);
		else
			sa4.setLimitXvars(MIN_XVARS);
		if (nop + delta_p >= MIN_NOP)
			delta_p -= 5;
		else {
			nop = MIN_NOP;
			delta_p = 0;
		}
		if (noem + delta_m >= MIN_NOEM)
			delta_m--;
		else {
			noem = MIN_NOEM;
			delta_m = 0;
		}
		log.debug(String.format(
				"DECREASE SUBPROBLEM SIZE CALLED: XVARS=%d NOP=%d NOM=%d",
				sa4.getLimitXvars(), nop + delta_p, noem + delta_m));
	}

	public void closeWindow() {
		app.closeWindow();
	}

	public int getRounds() {
		return round - 1;
	}
}
